<!DOCTYPE html>
<html lang="en">

    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>第 3 天</title>
        <style>
            #graph-container {
                position: absolute;
                top: 0;
                right: 0;
                bottom: 0;
                left: 0;
            }
            #input{
                position: absolute;
                top: -100px;
                left: -100px;
                width: 50px;
                display: none;
            }
        </style>
    </head>

    <body>
        <div id="graph-container"></div>
        <input type="text" id="input">
    </body>

    <script src="../js/g6.min.js"></script>
    <script>
        // 准备容器
        const container = document.getElementById('graph-container');
        const input = document.getElementById('input');
        const width = container.scrollWidth;
        const height = container.scrollHeight;

        // 准备数据
        const data = {
            id: "Modeling Methods",
            children: [
                {
                    id: "Classification",
                    text: '边备注',
                    children: [
                        { id: "Logistic regression" },
                        { id: "Linear discriminant analysis" },
                        { id: "Rules" },
                        { id: "Decision trees" },
                        { id: "Naive Bayes" },
                        { id: "K nearest neighbor" },
                        { id: "Probabilistic neural network" },
                        { id: "Support vector machine" }
                    ]
                },
                {
                    id: "Consensus",
                    children: [
                        {
                            id: "Models diversity",
                            children: [
                                { id: "Different initializations" },
                                { id: "Different parameter choices" },
                                { id: "Different architectures" },
                                { id: "Different modeling methods" },
                                { id: "Different training sets" },
                                { id: "Different feature sets" }
                            ]
                        },
                        {
                            id: "Methods",
                            children: [
                                { id: "Classifier selection" },
                                { id: "Classifier fusion" }
                            ]
                        },
                        {
                            id: "Common",
                            children: [
                                { id: "Bagging" },
                                { id: "Boosting" },
                                { id: "AdaBoost" }
                            ]
                        }
                    ]
                },
                {
                    id: "Regression",
                    children: [
                        { id: "Multiple linear regression" },
                        { id: "Partial least squares" },
                        { id: "Multi-layer feedforward neural network" },
                        { id: "General regression neural network" },
                        { id: "Support vector regression" }
                    ]
                }
            ]
        };

        // 实例化 G6
        const graph = new G6.TreeGraph({
            container: 'graph-container',
            width,
            height,
            modes: {
                default: [
                    {
                        type: 'collapse-expand',
                        onChange: function onChange(item, collapsed) {
                            item.get('model').collapsed = collapsed;
                            return true;
                        },
                    },
                ],
            },
            defaultEdge: {
                type: 'hvh-h',
                style: {
                    stroke: '#A3B1BF',
                },
            },
            defaultNode: {
                type: 'rect',
                style: {
                    stroke: '#A3B1BF',
                    width: 50,
                    height: 30,
                },
            },
            layout: {
                type: 'mindmap',
                direction: 'H',
                getHeight: () => {
                    return 16;
                },
                getWidth: () => {
                    return 16;
                },
                getVGap: () => {
                    return 10;
                },
                getHGap: () => {
                    return 50;
                },
            },
        });

        let node = null;
        // 注册自定义边
        G6.registerEdge('hvh-h', {
            draw(cfg, group) {
                const { startPoint, endPoint } = cfg;
                const startX = startPoint.x;
                const endX = endPoint.x;
                const startY = startPoint.y;
                const endY = endPoint.y;
                const Ydiff = endY - startY;
                // 获取 layout 方向
                const { direction } = graph.get('layout');

                let path = null;
                // 水平节点
                if (direction === 'H' && Ydiff === 0) {
                    path = [
                        ['M', startPoint.x, startPoint.y],
                        ['L', endPoint.x, endY],
                    ];
                } else {
                    const yOffset = 0.1; // 这里必须是非0数字
                    const xOffset = endX > startX ? 20 : -20; // 弧度x轴
                    const slope = endX > startX ? 40 : -40; // 边与起始点的距离(为0则从起始位置分叉)
                    // 节点第1个弧度的位置
                    const line1EndPoint = {
                        x: startPoint.x, // 横坐标
                        y: startPoint.y > 0 ? startPoint.y + yOffset : startPoint.y - yOffset, // 根据方向判断纵坐标位置
                    };
                    // 节点向右上方弯曲时的弧度坐标
                    const line2StartPoint = {
                        x: line1EndPoint.x + xOffset,
                        y: endY,
                    };
                    // 边分叉控制点的坐标
                    const controlPoint = {
                        x: ((line1EndPoint.x - startPoint.x) * (endY - startPoint.y)) / (line1EndPoint.y - startPoint.y) + startPoint.x,
                        y: endY, // 纵坐标 = 结束位置
                    };

                    path = [
                        ['M', startPoint.x, startPoint.y], // 起始位置
                        ['L', startPoint.x + slope, startPoint.y], // 向左/向右偏移量
                        ['L', line1EndPoint.x + slope, line1EndPoint.y], // 弯曲点的下方/上方坐标
                        ['Q', controlPoint.x + slope, controlPoint.y, line2StartPoint.x + slope, line2StartPoint.y], // 弧线坐标
                        ['L', endPoint.x, endY], // 目标节点坐标
                    ];
                }

                // 获取边的样式
                const { edgeStyle } = cfg.sourceNode.getModel();
                const shape = group.addShape('path', {
                    attrs: {
                        path,
                        stroke: '#1890FF',
                        endArrow: false,
                        ...cfg.style,
                        ...edgeStyle,
                    },
                    name: 'hvh-edge',
                    // 设置 draggable 以允许响应鼠标的图拽事件
                    draggable: true,
                });

                const { text } = cfg.targetNode.getModel();

                if (text) {
                    const line2 = endX > startX ? 50 : -80;

                    const label = group.addShape('text', {
                        attrs: {
                            x: startPoint.x + (endX > startX ? 20 : -20) + line2,
                            y: endY - 20,
                            text: text || '',
                            fill: '#333',
                            fontSize: 16,
                        },
                        name: 'hvh-edge-label',
                        zIndex: 10,
                    });

                    group.sort();
                    label.toFront();

                    // 编辑备注
                    label.on('click', ({ clientX, clientY }) => {
                        node = group.get('item');
                        input.style.display = 'block';
                        input.style.left = `${clientX - 20}px`;
                        input.style.top = `${clientY - 10}px`;
                        input.value = text;
                        input.focus();
                    });

                }
                return shape;
            },
        });

        // 输入框编辑文字
        input.onkeydown = ({ keyCode }) => {
            // 按下回车键
            if(keyCode === 13) {
                const target = node.get('target');
                const model = target.getModel();

                model.text = input.value;

                input.style.display = 'none';
                graph.update(target, model);
            }
        }

        // 传入数据
        graph.data(data);
        // 执行渲染
        graph.render();
        // 适应当前窗口大小
        graph.fitView();

        // 绑定事件:
        // 鼠标按下时改变鼠标形状
        graph.on('node:mousedown', function (e) {
            const model = e.item.getModel();

            model.style.cursor = 'grab';
            graph.update(e.item, model);
        });
        // 恢复鼠标形状
        graph.on('node:mouseup', function (e) {
            const model = e.item.getModel();

            model.style.cursor = null;
            graph.update(e.item, model);
        });

        graph.on('node:dragstart', function (e) {
            // 提升当前节点层级(防止拖拽时被其他节点遮挡)
            e.item.toFront();

            const model = e.item.getModel();
            model.style.cursor = 'grab';
            graph.update(e.item, model);
        });
        graph.on('node:drag', function (e) {
            const { clientX, clientY, item } = e;
            const point = graph.getPointByClient(clientX, clientY);

            item.updatePosition(point);
            // 当节点位置发生变化时，刷新所有节点位置，并重计算边的位置
            graph.refreshPositions();
        });
        graph.on('node:dragend', function (e) {
            const item = e.item;
            const model = item.getModel();

            model.style.cursor = null;
            graph.update(item, model);
        });
    </script>

</html>
